﻿//////////////////////////////////////////////////////////////////////////////
//This file is part of My Nes                                               //
//A Nintendo Entertainment System Emulator.                                 //
//                                                                          //
//Copyright © 2009 Ala Hadid (AHD)                                          //
//                                                                          //
//My Nes is free software; you can redistribute it and/or modify            //
//it under the terms of the GNU General Public License as published by      //
//the Free Software Foundation; either version 2 of the License, or         //
//(at your option) any later version.                                       //
//                                                                          //
//My Nes is distributed in the hope that it will be useful,                 //
//but WITHOUT ANY WARRANTY; without even the implied warranty of            //
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             //
//GNU General Public License for more details.                              //
//                                                                          //
//You should have received a copy of the GNU General Public License         //
//along with this program; if not, write to the Free Software               //
//Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA//
//////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace AHD.MyNes.Nes
{
    using mynessl.APU;
    using Sine;

    /*
     * WARNING !!
     * when you debug using visual studio debugger
     * the Microsoft.DirectX.DirectSound throws an exception !!
     * Press F5 to skip it.
     */
    public class APU
    {
        // Fields
        private const int _bitsPerSample = 0x10;
        private SecondaryBuffer _buffer;
        private const int _bufferLength = 5;
        private int _bufferSize;
        private bool[] _channelEnabled;
        private int _dataPosition;
        private bool _firstRender = true;
        private int _frameCounter;
        public uint _frameCycles = 29780;
        public uint _frameElapsedCycles;
        private int _lastPosition;
        public bool _palTiming;
        private const int _samplingRate = 0xac44;
        private APUChannel[] _soundChannels;
        private byte[] _soundData;
        private readonly NesMediaStreamSource parent;
        NesEmulator _Nes;
        bool IsPaused = false;
        int _MasterVolume = 7;
        uint k = 0;
        int bufferSize = 0x8000 * 4;
        public byte[] samplebuffer;
        private double _deltaT = 2;
        // Methods
        public APU(NesMediaStreamSource parent, NesEmulator NesEmu)
        {
            this.parent = parent;
            _Nes = NesEmu;
            this.InitDirectSound(parent);
            this.Reset();
            samplebuffer = new byte[bufferSize * 2];
            NoiseEnabled = false;

        }
        private void ClearBuffer()
        {
            for (int i = 0; i < this._bufferSize; i++)
            {
                this._soundData[i] = 0;
            }
            this._buffer.Write(0, this._soundData, true);
        }
        private void InitDirectSound(NesMediaStreamSource parent)
        {

            WaveFormat wfx = new WaveFormat();
            wfx.BitsPerSample = 0x8;
            wfx.Channels = 1;
            wfx.SamplesPerSecond = 0xac44;
            wfx.AverageBytesPerSecond = ((wfx.BitsPerSample / 8) * wfx.Channels) * wfx.SamplesPerSecond;
            wfx.BlockAlign = 2;
            BufferDescription desc = new BufferDescription(wfx);
            desc.BufferBytes = this._bufferSize = wfx.AverageBytesPerSecond * 5;

            this._buffer = new SecondaryBuffer(desc, parent);
            this._soundData = new byte[this._bufferSize];
            this.ClearBuffer();
            this._buffer.Play(0);
        }
        public void Pause()
        {
            if ((this._buffer != null) && !this._buffer.Disposed)
            {
                this._buffer.Stop();
                ClearBuffer();
                IsPaused = true;
            }
        }
        public void Play()
        {
            if (!IsPaused)
            { return; }
            if ((this._buffer != null) && !this._buffer.Disposed)
            {
                IsPaused = false;
                ClearBuffer();
                this._buffer.Play(0);
            }
        }
        public byte ReadStatusReg()
        {
            int num = 0;
            num |= this._soundChannels[0].Enabled ? 1 : 0;
            num |= this._soundChannels[1].Enabled ? 2 : 0;
            num |= this._soundChannels[2].Enabled ? 4 : 0;
            num |= this._soundChannels[3].Enabled ? 8 : 0;
            num |= this._soundChannels[4].Enabled ? 0x10 : 0;
            //num |= 0x40;
            return (byte)num;
        }

        private int bufferpos = 0;

        public void Render()
        {
           
            if (IsPaused)
            {
                return;
            }
            //int writePosition = 0;
            //this.UpdateRegisters();
            byte num4 = (byte)(GetMixed());

            //Add

            samplebuffer[bufferpos % samplebuffer.Length] = num4;//(byte)Math.Round((decimal)(num4 + samplebuffer[bufferpos % samplebuffer.Length]) / 2);
            bufferpos++;

        }



        private byte GetMixed()
        {
            float mixed = 0.0f;
            float sample = 0;
            byte num4 = 0;



            for (int j = 2; j < 3; j++)
            {


                if (this._channelEnabled[j])
                {
                    sample = this._soundChannels[j].RenderSample() / 256.0f;
                    mixed += sample;
                }
            }
            mixed *= 3.2f * (_MasterVolume / 10.0f);
            if (mixed > 1.0f) mixed = 1.0f;
            if (mixed < -1.0f) mixed = -1.0f;

            num4 = (byte)(mixed * 256.0f);
            return num4;
            //return result;
        }

        public void DropFrame()
        {
            //if (bufferpos != 0)
            //    _deltaT *= ((double)_samplingRate / 60) / bufferpos;

            List<byte> result = new List<byte>(bufferpos);
            double itemPerByte = bufferpos / (_samplingRate / 50.0);
            double summB = 0;
            byte b;
            for(int i = 0; i < bufferpos ; i++)
            {
                if (i >= samplebuffer.Length)
                    break;
                b = samplebuffer[i];
                summB += b;
                if (i > (itemPerByte * (result.Count + 1)) || i == bufferpos - 1)
                {
                    //result.Add((byte)Math.Round((double)summB / itemPerByte));
                    result.Add(b);
                    summB = 0;
                }
                
            }



            parent.write(result.ToArray(), 0, result.Count);
            result.Clear();
            bufferpos = 0;


        }




        private void UpdateRegisters()
        {


            //int num = this._palTiming ? (this._frameCounter % 5) : (this._frameCounter % 4);
            if (_frameCounter == 0x2000)
            {

                _frameCounter = 0;
                for (int i = 0; i < 5; i++)
                {
                    APUChannel base2 = this._soundChannels[i];

                    //switch (num)
                    //{
                    //case 0:
                    //case 2:
                    //    base2.UpdateEnvelope();
                    //    base2.UpdateLinearCounter();
                    //    break;

                    //case 1:
                    //case 3:

                    base2.DecrementLengthCounter();
                    base2.UpdateEnvelope();
                    base2.UpdateSweep();
                    base2.UpdateLinearCounter();

                    //            break;
                }
            }

            //}

            this._frameCounter++;
        }
        public void Reset()
        {
            this._soundChannels = new APUChannel[] { new Channel_Square(44100.0, true), new Channel_Square(44100.0, false), new Channel_Triangle(44100.0), new Channel_Noise(44100.0), new Channel_DMC(44100.0, _Nes) };
            this._channelEnabled = new bool[5];
            for (int i = 0; i < 5; i++)
            {
                this._channelEnabled[i] = true;
            }
        }
        public void Shutdown()
        {
            if ((this._buffer != null) && !this._buffer.Disposed)
            {
                this._buffer.Stop(); IsPaused = true;
            }
            this._buffer.Dispose();
        }
        public void WriteDMCReg1(byte b)
        {
            this._soundChannels[4].WriteReg1(b);
        }
        public void WriteDMCReg2(byte b)
        {
            this._soundChannels[4].WriteReg2(b);
        }
        public void WriteDMCReg3(byte b)
        {
            this._soundChannels[4].WriteReg3(b);
        }
        public void WriteDMCReg4(byte b)
        {
            this._soundChannels[4].WriteReg4(b);
        }
        public void WriteNoiseReg1(byte b)
        {
            this._soundChannels[3].WriteReg1(b);
        }
        public void WriteNoiseReg2(byte b)
        {
            this._soundChannels[3].WriteReg2(b);
        }
        public void WriteNoiseReg3(byte b)
        {
            this._soundChannels[3].WriteReg3(b);
        }
        public void WriteRectReg1(int c, byte b)
        {
            this._soundChannels[c].WriteReg1(b);
        }
        public void WriteRectReg2(int c, byte b)
        {
            this._soundChannels[c].WriteReg2(b);
        }
        public void WriteRectReg3(int c, byte b)
        {
            this._soundChannels[c].WriteReg3(b);
        }
        public void WriteRectReg4(int c, byte b)
        {
            this._soundChannels[c].WriteReg4(b);
        }
        public void WriteStatusReg(byte b)
        {
            this._soundChannels[0].Enabled = (b & 1) != 0;
            this._soundChannels[1].Enabled = (b & 2) != 0;
            this._soundChannels[2].Enabled = (b & 4) != 0;
            this._soundChannels[3].Enabled = (b & 8) != 0;
            this._soundChannels[4].Enabled = (b & 0x10) != 0;
        }
        public void WriteTriReg1(byte b)
        {
            this._soundChannels[2].WriteReg1(b);
        }
        public void WriteTriReg2(byte b)
        {
            this._soundChannels[2].WriteReg2(b);
        }
        public void WriteTriReg3(byte b)
        {
            this._soundChannels[2].WriteReg3(b);
        }
        public void WriteTriReg4(byte b)
        {
            this._soundChannels[2].WriteReg4(b);
        }
        public void SetClockSpeed(int ClockSpeed)
        {
            for (int j = 0; j < 5; j++)
            {
                _soundChannels[j].ClockSpeed = ClockSpeed;
                _soundChannels[j].EnvelopeMult = 1; // ClockSpeed / 192;
                _soundChannels[j].SweepMult = 1; // ClockSpeed / 96;
            }

            APUChannel._ClockSpeed = ClockSpeed;

        }
        // Properties
        public bool DMCEnabled
        {
            get
            {
                return this._channelEnabled[4];
            }
            set
            {
                this._channelEnabled[4] = value;
            }
        }
        public bool NoiseEnabled
        {
            get
            {
                return this._channelEnabled[3];
            }
            set
            {
                this._channelEnabled[3] = value;
            }
        }
        public bool SquareWave1Enabled
        {
            get
            {
                return this._channelEnabled[0];
            }
            set
            {
                this._channelEnabled[0] = value;
            }
        }
        public bool SquareWave2Enabled
        {
            get
            {
                return this._channelEnabled[1];
            }
            set
            {
                this._channelEnabled[1] = value;
            }
        }
        public bool TriangleEnabled
        {
            get
            {
                return this._channelEnabled[2];
            }
            set
            {
                this._channelEnabled[2] = value;
            }
        }
        public int MasterVolume
        { get { return _MasterVolume; } set { _MasterVolume = value; } }

    }

    public class WaveFormat
    {
        public int BitsPerSample;
        public int Channels;
        public int SamplesPerSecond;
        public int AverageBytesPerSecond;
        public int BlockAlign;
    }
}